package com.umpay.business.ebankpay.impl;

import com.umpay.business.ebankpay.EbankPayBusiness;
import com.umpay.controller.base.AbstractController;
import com.umpay.dict.DataDict;
import com.umpay.dict.XmlData;
import com.umpay.dict.XmlData4Mer;
import com.umpay.dict.retcode.MerAccessEC;
import com.umpay.dict.retcode.MerBusiEC;
import com.umpay.dto.UMFResponse.Message;
import com.umpay.dto.UMFResponse.Message.Meta;
import com.umpay.dto.memberMgr.res.MerInfRes;
import com.umpay.dto.merAccess.req.ebankpay.EbankPayReq;
import com.umpay.dto.merBusi.req.ebankpay.EbankPayNotifyBusiReq;
import com.umpay.dto.merBusi.req.ebankpay.EbankPayOrderBusiReq;
import com.umpay.dto.merBusi.req.refund.RefundCmdBusiReq;
import com.umpay.dto.merBusi.res.ebankpay.EbankPayNotifyBusiRes;
import com.umpay.dto.merBusi.res.ebankpay.EbankPayOrderBusiRes;
import com.umpay.exception.BusinessException;
import com.umpay.exception.RedirectBusiException;
import com.umpay.feign.client.ConsumeBusiServerFeignClient;
import com.umpay.feign.client.MemberDomainServerFeignClient;
import com.umpay.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: EbankPayBusinessImpl  
 * @Description: 网银支付业务处理
 * @author zhangtao  
 * @date May 24, 2018
 */
@Component
public class EbankPayBusinessImpl extends AbstractController implements EbankPayBusiness {
    
	private static final Logger log = LoggerFactory.getLogger(EbankPayBusinessImpl.class);
    
	@Autowired
	private PropertyUtil propertyUtil;
	
	@Autowired
	private ConsumeBusiServerFeignClient consumeBusiServerFeignClient;
	
	@Autowired
    private SignatureAuthUtil signatureAuthUtil;
	@Autowired
	private MemberDomainServerFeignClient memberDomainServerFeignClient;
	@Override
	public ModelAndView ebankPayOrder(EbankPayReq ebankPayReq) {
		log.info("【网银支付申请--步骤1】请求参数校验");
		checkRequestParameter(ebankPayReq);
		log.info("【网银支付申请--步骤2】请求业务线支付申请");
		Message<EbankPayOrderBusiRes> merBusiRes = requestBusiPayApply(ebankPayReq);
		log.info("【网银支付跳转网银WEB--步骤3】跳转网银WEB");
		return redirectPayWebPage(ebankPayReq,merBusiRes.getData());
	}

	@Override
	public ModelAndView ebankPayFrontNotify(Map<String, Object> reqMap) {
		log.info("【网银支付前台通知--步骤1】请求参数校验");
		checkNotifyRequestParameter(reqMap);
		log.info("【网银支付前台通知--步骤2】请求业务线确认结果");
		Message<EbankPayNotifyBusiRes> merBusiRes = requestBusiPayConfirm(reqMap,DataDict.NOTIFY_TYPE_FRONT);
		log.info("【网银支付前台通知--步骤3】通知商户支付结果");
		return redirectMerPage(merBusiRes);
	}

	@Override
	public void ebankPayBackNotify(Map<String, Object> reqMap, HttpServletResponse response) {
		log.info("【网银支付后台通知--步骤1】请求参数校验");
		checkNotifyRequestParameter(reqMap);
		log.info("【网银支付后台通知--步骤2】请求业务线确认结果");
		Message<EbankPayNotifyBusiRes> merBusiRes = requestBusiPayConfirm(reqMap,DataDict.NOTIFY_TYPE_BACKGROUND);
		log.info("【网银支付后台通知--步骤3】响应paygate");
		assembleResponseData(merBusiRes,response);
		
	}
	private Message<EbankPayOrderBusiRes> requestBusiPayApply(EbankPayReq ebankPayReq){
		EbankPayOrderBusiReq busiReq = new EbankPayOrderBusiReq();
		String bProductId = StringUtils.trim(propertyUtil.getProperty("bsp.bproductid"));  //基础业务产品
		String pProductId = "";
		String payType = StringUtils.trim(ebankPayReq.getPay_type());
		if(DataDict.B2BBANK.equals(payType)) {
			pProductId = StringUtils.trim(propertyUtil.getProperty("bsp.b2bbank.pay.pproductid"));
		}else if(DataDict.B2CBANK.equals(payType)) {
			pProductId = StringUtils.trim(propertyUtil.getProperty("bsp.b2cbank.pay.pproductid"));
		}else if(DataDict.B2CDEBITBANK.equals(payType)){
			pProductId = StringUtils.trim(propertyUtil.getProperty("bsp.b2cdebitbank.pay.pproductid"));
		}else {
			throw new BusinessException(MerAccessEC.REQPARAM_ERR, String.format("不支持[%s]类型的网银支付", payType));
		}
		busiReq.setRpid(RpidUtils.getRpid());
		busiReq.setMerId(StringUtils.trim(ebankPayReq.getMer_id()));
		busiReq.setbProductId(bProductId);
		busiReq.setpProductId(pProductId);
		busiReq.setNotifyUrl(StringUtils.trim(ebankPayReq.getNotify_url()));
		busiReq.setRetUrl(StringUtils.trim(ebankPayReq.getRet_url()));
		busiReq.setMerTrace(StringUtils.trim(ebankPayReq.getMer_trace()));
		busiReq.setTradeNo(StringUtils.trim(ebankPayReq.getTrade_no()));
		busiReq.setPayType(payTypeConvert(StringUtils.trim(ebankPayReq.getPay_type())));
		busiReq.setGateId(StringUtils.trim(ebankPayReq.getGate_id()));
		busiReq.setAmount(StringUtils.trim(ebankPayReq.getAmount()));
		busiReq.setUserIp(StringUtils.trim(ebankPayReq.getUser_ip()));
		busiReq.setGoodsId(StringUtils.trim(ebankPayReq.getGoods_id()));
		busiReq.setGoodsInf(StringUtils.trim(ebankPayReq.getGoods_inf()));
		busiReq.setRemark(StringUtils.trim(ebankPayReq.getRemark()));
		busiReq.setVersion(DataDict.VERSION);
		busiReq.setReqDate(TimeUtil.date8());
		busiReq.setReqTime(TimeUtil.time6());
		busiReq.setAccessName(StringUtils.trim(ebankPayReq.getAccessName()));
		busiReq.setAccessType(StringUtils.trim(ebankPayReq.getAccessType()));
		busiReq.setDepBankMerId(StringUtils.trim(ebankPayReq.getDepBankMerId()));
		Message<EbankPayOrderBusiRes> merBusiRes = consumeBusiServerFeignClient.ebankPayOrder(busiReq);
		
        //判断业务响应结果，不为空
		if (merBusiRes != null) {
			Meta merBusiRet = merBusiRes.getMeta();
			String retCode = merBusiRet.getRet_code();
			String retMsg = merBusiRet.getRet_msg();
			if(!MerAccessEC.SUCCESS.equals(retCode)) { //下单不成功就抛异常
				throw new RedirectBusiException(retCode,retMsg);
			}
		} else {//响应结果为空，交易超时
			throw new RedirectBusiException(MerAccessEC.RESULT_UNKNOWN, "交易结果不明");
		}
		merBusiRes.getData().setpProductId(pProductId);
		return merBusiRes;
	}
	private String payTypeConvert(String payType) {
		String convertPayType = null;
		if(DataDict.B2BBANK.equals(payType)){
			convertPayType = DataDict.PAY_TYPE_B2BBANK_1;
		}else if(DataDict.B2CBANK.equals(payType)){
			convertPayType = DataDict.PAY_TYPE_B2CBANK_12;
		}else if(DataDict.B2CDEBITBANK.equals(payType)){
			convertPayType = DataDict.PAY_TYPE_B2CBANK_2;
		}else{
			log.error("【网银支付】传入的支付方式有误！");
			throw new RedirectBusiException(MerAccessEC.REQPARAM_ERR, "请求参数有误");
		}
		return convertPayType;
	}
	private  Map<String, String> mapTransfer(Map<String, Object> reqMap) {
		Map<String, String> retMap = new HashMap<String, String>();
		for(Map.Entry<String, Object> entry:reqMap.entrySet()){
			retMap.put(entry.getKey(),StringUtils.trim(entry.getValue()));
		}
		return retMap;
	}
	private void checkRequestParameter(EbankPayReq ebankPayReq) {
		log.info("请求参数校验开始");
		Map<String, Object> reqMap = EntityMapUtil.transBean2Map(ebankPayReq);
		try {
			ReqParaCheckUtil.checkRequireAndRegular(mapTransfer(reqMap), "Regex.ebankpay.rule");
		} catch (BusinessException e) {
			String errCode = e.getCode();
            String errMsg = e.getMessage();
            throw new RedirectBusiException(errCode,errMsg);
		}
		log.info("请求参数校验通过");
		String payType = StringUtils.trim(ebankPayReq.getPay_type());
		if(!(DataDict.B2BBANK.equals(payType) || DataDict.B2CBANK.equals(payType) || DataDict.B2CDEBITBANK.equals(payType))) {
			throw new RedirectBusiException(MerAccessEC.REQPARAM_ERR, String.format("不支持[%s]类型的网银支付", payType));
		}
	}
	
	private ModelAndView redirectPayWebPage(EbankPayReq ebankPayReq,EbankPayOrderBusiRes merBusiRes) {
		Map<String, String> reqMap = new HashMap<String, String>();
		reqMap.put(XmlData.RPID, merBusiRes.getRpid());
		reqMap.put(XmlData.REQDATE, TimeUtil.date8());
		reqMap.put(XmlData.REQTIME, TimeUtil.time6());
		reqMap.put(XmlData.INSTID, merBusiRes.getInstId());
		reqMap.put(XmlData.TRACE, merBusiRes.getTrace());
		reqMap.put(XmlData.MERID, merBusiRes.getMerId());
		reqMap.put(XmlData.SERVTYPE, merBusiRes.getServType());
		reqMap.put(XmlData.ORDERID, merBusiRes.getOrderId());
		reqMap.put(XmlData.ORDERDATE, merBusiRes.getOrderDate());
		reqMap.put(XmlData.AMOUNT, merBusiRes.getAmount());
		reqMap.put(XmlData.PPRODUCTID,merBusiRes.getpProductId());
		reqMap.put(XmlData.RETURL, UrlGainUtil.getServiceUrl("Url.merAccess.outer.base.url", "ebankpay.front.notify.access"));
		reqMap.put(XmlData.NOTIFYURL, UrlGainUtil.getServiceUrl("Url.merAccess.inner.base.url", "ebankpay.back.notify.access"));
		reqMap.put(XmlData.USERID, StringUtils.trim(merBusiRes.getUserId()));
		reqMap.put(XmlData.MERCUSTID, StringUtils.trim(merBusiRes.getMerCustId()));
		reqMap.put(XmlData.GATEID, StringUtils.trim(ebankPayReq.getGate_id()));
		reqMap.put(XmlData.PAYTYPE,  StringUtils.trim(ebankPayReq.getPay_type()));
		reqMap.put(XmlData.GOODSID,  StringUtils.trim(ebankPayReq.getGoods_id()));
		reqMap.put(XmlData.EXPIRETIME, StringUtils.trim(ebankPayReq.getExpire_time()));
		try {
			//对于goodsinf可能存在中文，会导致验签失败，所以用base64为加密
			reqMap.put(XmlData.GOODSINF, new String(Base64Util.encode((StringUtils.trim(ebankPayReq.getGoods_inf()).getBytes("UTF-8")))) );
		} catch (UnsupportedEncodingException e) {
			log.error("商品描述信息base64加密有误");
			throw new RedirectBusiException(MerAccessEC.EN_DE_CRYPTION_ERROR,"商品描述信息base64加密有误");
		}
		reqMap.put(XmlData.USERIP, StringUtils.trim(ebankPayReq.getUser_ip()));
		String paywebBaseUrl = StringUtils.trim(propertyUtil.getProperty("Url.payweb.base.url"));
		String serviceUrl = "/ebankPayOrder.htm"; //网银下单
		if(StringUtils.isEmpty(paywebBaseUrl)) {
			throw new RedirectBusiException(MerAccessEC.SYSTEM_PARAM_NOT_CONFIGURE,"请求payWeb地址未配置");
		}
		String  sign = signatureAuthUtil.signMsgFront(reqMap);
		reqMap.put(XmlData.SIGN, sign);
		return new ModelAndView(new RedirectView(paywebBaseUrl + serviceUrl), reqMap);
	}
	private ModelAndView redirectMerPage(Message<EbankPayNotifyBusiRes> merBusiRes) {
		try {
			String retCode = null;
			String retMsg = null;
			if(merBusiRes !=null) {
				String notifyPayState = merBusiRes.getData().getNotifyPayState();
				retCode = merBusiRes.getMeta().getRet_code();
				if(StringUtils.isEmpty(notifyPayState)) {
					retCode = MerAccessEC.RESULT_UNKNOWN;//支付不明 
					retMsg = "支付不明";
				}else if(notifyPayState.equals(DataDict.PAYSTATE_SUCCESS)){
					retCode = MerAccessEC.SUCCESS;//支付成功
					retMsg = "支付成功";
				}else if(notifyPayState.equals(DataDict.PAYSTATE_H5PAY_FAILED)) {
					retCode = MerBusiEC.CONSUME_FAIL;//支付失败
					retMsg = "支付失败";
				}else {
					retCode = MerAccessEC.RESULT_UNKNOWN;//支付不明 
					retMsg = "支付不明";
				}
			}
			else {
				retCode = MerAccessEC.RESULT_UNKNOWN;//支付不明 
				retMsg = "支付不明";
			}
			String retUrl = StringUtils.trim(merBusiRes.getData().getRetUrl());
			Message<Map<String,String>> notifyMessage = new Message<Map<String,String>>();
			notifyMessage.setMeta(new Meta(retCode,retMsg));
			Map<String,String> notifyMap = new HashMap<String,String>();
			EbankPayNotifyBusiRes resInfo = merBusiRes.getData();
			if(resInfo != null) {
				notifyMap.put(XmlData4Mer.MER_ID, resInfo.getMerId());
				notifyMap.put(XmlData4Mer.SETTLE_AMT, resInfo.getSettleAmt());
				notifyMap.put(XmlData4Mer.VERSION,resInfo.getVersion());
				notifyMap.put(XmlData4Mer.ORDER_ID,resInfo.getOrderId());
				notifyMap.put(XmlData4Mer.MER_TRACE,resInfo.getMerTrace());
				notifyMap.put(XmlData4Mer.AMOUNT,resInfo.getAmount());
				notifyMap.put(XmlData4Mer.TRADE_NO,resInfo.getTradeNo());
				notifyMap.put(XmlData4Mer.MER_CHECK_DATE,resInfo.getMerCheckDate());
			}
			notifyMessage.setData(notifyMap);
			String  redirectUrl = "redirect:" +filterMerRetUrl(retUrl)+ getAssembleFrontUrl(notifyMessage);
			log.info(String.format("通知商户支付结果[%s]", redirectUrl));
            return new ModelAndView(redirectUrl);
		}catch(BusinessException e){
            log.error(String.format("【网银支付前台通知】网银支付通知异常：[%s：%s]",e.getCode(), e.getMessage()));
            throw new BusinessException(e.getCode(), e.getMessage());
		}catch(Exception e){
            log.error(String.format("【网银支付前台通知】网银支付通知异常：[%s：%s]",MerAccessEC.SYSTEM_ERR, e.getMessage()));
            throw new BusinessException(MerAccessEC.SYSTEM_ERR, "系统忙，请稍后再试");
        }
	}
	private void assembleResponseData(Message<EbankPayNotifyBusiRes> merBusiRes,HttpServletResponse response) {
		if(merBusiRes != null) {
			String notifyPayState = merBusiRes.getData().getNotifyPayState();
			//是终态直接响应，不需要继续通知
			if(notifyPayState.equals(DataDict.PAYSTATE_SUCCESS) || notifyPayState.equals(DataDict.PAYSTATE_H5PAY_FAILED)) {
				String retCode = MerAccessEC.SUCCESS;
				String retMsg = "确认完成";
				merBusiRes.setMeta(new Meta(retCode,retMsg));
				PrintWriter pw;
				try {
					pw = response.getWriter();
					log.info(String.format("网银支付确认后台通知返回给支付网关数据为：%s",merBusiRes));
					pw.write(merBusiRes.toString());
					pw.close();
				} catch (IOException e) {
					log.info(MerAccessEC.SYSTEM_ERR,"【网银支付确认后台通知】响应支付网关异常！");
					e.printStackTrace();
				}
			}
		}else {
			throw new BusinessException(MerAccessEC.SYSTEM_ERR, "系统异常");
		}
	}
	private Message<EbankPayNotifyBusiRes> requestBusiPayConfirm(Map<String, Object> reqMap,String notifyType) {
		EbankPayNotifyBusiReq busiReq = new EbankPayNotifyBusiReq();
		String bProductId = StringUtils.trim(propertyUtil.getProperty("bsp.bproductid"));  //基础业务产品
//		String pProductId = "";
//		String payType = StringUtils.trim(reqMap.get(XmlData.PAYTYPE));
//		if(DataDict.B2BBANK.equals(payType)) {
//			pProductId = StringUtils.trim(propertyUtil.getProperty("bsp.b2bbank.pay.pproductid"));
//		}else if(DataDict.B2CBANK.equals(payType)) {
//			pProductId = StringUtils.trim(propertyUtil.getProperty("bsp.b2cbank.pay.pproductid"));
//		}else if(DataDict.B2CDEBITBANK.equals(payType)){
//			pProductId = StringUtils.trim(propertyUtil.getProperty("bsp.b2cdebitbank.pay.pproductid"));
//		}else {
//			throw new BusinessException(MerAccessEC.REQPARAM_ERR, String.format("不支持[%s]类型的网银支付", payType));
//		}
		if(DataDict.NOTIFY_TYPE_FRONT.equals(notifyType)) {
			busiReq.setRpid("FRONT_" + StringUtils.trim(reqMap.get(XmlData.TRACE)));
		}else {
			busiReq.setRpid("BACK_" + StringUtils.trim(reqMap.get(XmlData.TRACE)));
		}
		busiReq.setMerId(StringUtils.trim(reqMap.get(XmlData.MERID)));
		busiReq.setbProductId(bProductId);
		busiReq.setInstId(StringUtils.trim(reqMap.get(XmlData.INSTID)));
		busiReq.setTrace(StringUtils.trim(reqMap.get(XmlData.TRACE)));
		busiReq.setAmount(StringUtils.trim(reqMap.get(XmlData.AMOUNT)));
		busiReq.setStlDate(StringUtils.trim(reqMap.get(XmlData.STLDATE)));
		busiReq.setPayState(StringUtils.trim(reqMap.get(XmlData.PAYSTATE)));
		busiReq.setRetCode(StringUtils.trim(reqMap.get(XmlData.RETCODE)));
		busiReq.setRetMsg(StringUtils.trim(reqMap.get(XmlData.RETMSG)));
		busiReq.setBankCheckDate(StringUtils.trim(reqMap.get(XmlData.BANKCHECKDATE)));
		busiReq.setNotifyType(notifyType);
		busiReq.setVersion(DataDict.VERSION);
		busiReq.setReqDate(TimeUtil.date8());
		busiReq.setReqTime(TimeUtil.time6());
		MerInfRes merInfRes = merInfoQuery(busiReq);
		busiReq.setAccessName(StringUtil.trim(merInfRes.getAccessName()));
		busiReq.setAccessType(StringUtil.trim(merInfRes.getAccessType()));
		busiReq.setDepBankMerId(StringUtil.trim(merInfRes.getDepBankMerId()));
		Message<EbankPayNotifyBusiRes> merBusiRes = consumeBusiServerFeignClient.ebankPayNotify(busiReq);
		return merBusiRes;
	}
	
	public MerInfRes merInfoQuery(EbankPayNotifyBusiReq busiReq){
		MerInfRes merInfo =null;
		String merId = StringUtil.trim(busiReq.getMerId());
		String rpid = StringUtil.trim(busiReq.getRpid());
		if (StringUtil.isEmpty(merId)) {
			log.error("【平台信息查询】查询平台信息失败，merId is null");
			throw new BusinessException(MerBusiEC.RESULT_UNKNOW, "查询平台信息失败");
		}
		log.info(String.format("请求平台商户信息,参数为:[%s]", merId));
		Message<MerInfRes> merRes  = memberDomainServerFeignClient.getMerInfo(merId,rpid);
		if (merRes == null) {
			throw new BusinessException(MerBusiEC.RESULT_UNKNOW, "查询平台信息失败");
		}
		
		Meta meta = merRes.getMeta();
		if (meta == null) {
			throw new BusinessException(MerBusiEC.RESULT_UNKNOW, "查询平台信息失败");
		}
		String retCode = meta.getRet_code();
		String retMsg = meta.getRet_msg();
		if (StringUtil.isEmpty(retCode)) {
			log.error("【平台信息查询】查询结果未明");
			throw new BusinessException(MerBusiEC.RESULT_UNKNOW, "查询平台信息失败");
		} else if (MerBusiEC.SUCCESS.equals(retCode)) {
			merInfo = merRes.getData();
			if (merInfo == null) {
				log.error("【平台信息查询】未查询到相关信息");
				throw new BusinessException(MerBusiEC.MER_CLOSE, "未查到相关信息");
			} else {
				String state = StringUtil.trim(merInfo.getState());
				if (!DataDict.MER_STATE_NORMAL_2.equals(state)) {
					log.error("【平台信息查询】平台状态未开通");
					throw new BusinessException(MerBusiEC.CRM_PLATSTATE_CANCEL, "平台状态未开通");
				}else{
					log.info(String.format("【平台信息查询】平台状态正常：平台号：%s,平台名称：%s", merInfo.getMerId(),merInfo.getMerName()));
				}
			}
		} else {
			log.error("【平台信息查询】查询平台信息出错！");
			throw new BusinessException(retCode, retMsg);
		}
		return merInfo;
	}
	
    private void checkNotifyRequestParameter(Map<String, Object> reqMap) {
    	 log.info("验证支付网关签名数据");
    	 Map<String, Object> paraMap = new HashMap<String, Object>(reqMap);
         String sign = StringUtils.trim(reqMap.get(XmlData.SIGN));
         String key = StringUtils.trim(propertyUtil.getProperty("payGate.desSign.key"));
         paraMap.remove(XmlData.SIGN);
         paraMap.remove(XmlData.RETMSG);
         boolean isSign = HashSignHelper.verifySign(sign, paraMap, key); 
         if(!isSign){
         	throw new BusinessException(MerAccessEC.MERSIGN_ERR,"验证网关签名失败");
         }
    }

    /**
     * 商户前台通知地址过滤
     * 兼容部分商户前台通知地址带有参数(例如:http://hlshop.qiandzh.cn/index.php?ctl=Buyer_Order&met=physical)
     * 如果商户地址已带有?,则在地址末尾追加&
     * 否则追加?
     * @param retUrl
     * @return
     */
    private String filterMerRetUrl(String retUrl) {
        if (StringUtils.isEmpty(retUrl)) {
            log.warn(String.format("商户前台通知地址过滤,通知地址[%s]为空",retUrl));
            return "";
        }
        if (retUrl.contains("?")) {
            return retUrl + "&";
        } else {
            return retUrl + "?";
        }
    }

//    public static void main(String[] args) {
//        String returl = "http://www.baidu.com?ctl=Buyer_Order&met=physical";
//        String returl1 = "http://www.baidu.com/merAccess/retController?lala=lala";
//        System.out.println(new EbankPayBusinessImpl().filterMerRetUrl(returl1));
//    }

}
